#include "biosInfo.h"
#include <iostream>

void 
BiosInfos::setGeneral(UINT8 len, const std::string &vendor,
                      const std::string &version, UINT16 addrSeg,
                      const std::string &date, UINT8 romSize,
                      UINT64 character)
{
    _length = len;
    _vendor = vendor;
    _version = version;
    _startAddressSegment = addrSeg;
    _releaseDate = date;
    _romSize = romSize;
    _characteristics = character;
}

void 
BiosInfos::print() const
{
    std::cout << "BIOS Information:" << std::endl;
    std::cout << "\tVendor: " << _vendor << std::endl;
    std::cout << "\tVersion: " << _version << std::endl;
    std::cout << "\tRelease Date: " << _releaseDate << std::endl;

    if (_startAddressSegment != 0) {
        printf("\tAddress: 0x%04X0\n", _startAddressSegment);
        printBiosRuntimeSize((0x10000 - _startAddressSegment) << 4);
    }
    printBiosRomSize(_romSize, _extendedBiosROMSize);
    std::cout << "\tCharacteristics:" << std::endl;
    printBiosCharacteristics(_characteristics);
	if (_length < 0x13)
		return;
	PrintBiosCharacteristicsX1(_characteristicsExtensionBytes & 0xFF);
	if (_length < 0x14)
		return;
	PrintBiosCharacteristicsX2((_characteristicsExtensionBytes >> 8) & 0xFF);
	if (_length< 0x18)
		return;
	
	if (_majorRelease != 0xFF && _minorRelease != 0xFF)
		printf("\tBIOS Revision: %u.%u\n", _majorRelease, _minorRelease);
	if (_ecfMajorRelease != 0xFF && _ecfMinorRelease != 0xFF)
		printf("\tEmbedded Controller Revision: %u.%u\n", _ecfMajorRelease, _ecfMinorRelease);
}

void 
BiosInfos::PrintBiosCharacteristicsX1(UINT8 code) const
{
	/* 7.1.2.1 */
	static const char *characteristics[] = {
		"ACPI is supported", /* 0 */
		"USB legacy is supported",
		"AGP is supported",
		"I2O boot is supported",
		"LS-120 boot is supported",
		"ATAPI Zip drive boot is supported",
		"IEEE 1394 boot is supported",
		"Smart battery is supported" /* 7 */
	};
	int i;

	for (i = 0; i <= 7; i++)
		if (code & (1 << i))
			printf("\t\t%s\n", characteristics[i]);
}

void 
BiosInfos::PrintBiosCharacteristicsX2(UINT8 code) const
{
	/* 37.1.2.2 */
	static const char *characteristics[] = {
		"BIOS boot specification is supported", /* 0 */
		"Function key-initiated network boot is supported",
		"Targeted content distribution is supported",
		"UEFI is supported",
		"System is a virtual machine",
		"Manufacturing mode is supported",
		"Manufacturing mode is enabled" /* 6 */
	};
	int i;

	for (i = 0; i <= 6; i++)
		if (code & (1 << i))
			printf("\t\t%s\n", characteristics[i]);
}

void 
BiosInfos::printBiosCharacteristics(UINT64 code) const
{
	/* 7.1.1 */
	static const char *characteristics[] = {
		"BIOS characteristics not supported", /* 3 */
		"ISA is supported",
		"MCA is supported",
		"EISA is supported",
		"PCI is supported",
		"PC Card (PCMCIA) is supported",
		"PNP is supported",
		"APM is supported",
		"BIOS is upgradeable",
		"BIOS shadowing is allowed",
		"VLB is supported",
		"ESCD support is available",
		"Boot from CD is supported",
		"Selectable boot is supported",
		"BIOS ROM is socketed",
		"Boot from PC Card (PCMCIA) is supported",
		"EDD is supported",
		"Japanese floppy for NEC 9800 1.2 MB is supported (int 13h)",
		"Japanese floppy for Toshiba 1.2 MB is supported (int 13h)",
		"5.25\"/360 kB floppy services are supported (int 13h)",
		"5.25\"/1.2 MB floppy services are supported (int 13h)",
		"3.5\"/720 kB floppy services are supported (int 13h)",
		"3.5\"/2.88 MB floppy services are supported (int 13h)",
		"Print screen service is supported (int 5h)",
		"8042 keyboard services are supported (int 9h)",
		"Serial services are supported (int 14h)",
		"Printer services are supported (int 17h)",
		"CGA/mono video services are supported (int 10h)",
		"NEC PC-98" /* 31 */
	};

	/*
	 * This isn't very clear what this bit is supposed to mean
	 */
	if (code.l & (1 << 3)) {
		printf("\t\t%s\n", characteristics[0]);
		return;
	}

	for (int i = 4; i <= 31; i++)
		if (code.l & (1 << i))
			printf("\t\t%s\n", characteristics[i - 3]);
}

/* shift is 0 if the value is in bytes, 1 if it is in kilobytes */
void 
BiosInfos::prinMemorySize(const char *attr, UINT64 code, int shift) const
{
	unsigned long capacity;
	UINT16 split[7];
	static const char *unit[8] = {
		"bytes", "kB", "MB", "GB", "TB", "PB", "EB", "ZB"
	};
	int i;

	/*
	 * We split the overall size in powers of thousand: EB, PB, TB, GB,
	 * MB, kB and B. In practice, it is expected that only one or two
	 * (consecutive) of these will be non-zero.
	 */
	split[0] = code.l & 0x3FFUL;
	split[1] = (code.l >> 10) & 0x3FFUL;
	split[2] = (code.l >> 20) & 0x3FFUL;
	split[3] = ((code.h << 2) & 0x3FCUL) | (code.l >> 30);
	split[4] = (code.h >> 8) & 0x3FFUL;
	split[5] = (code.h >> 18) & 0x3FFUL;
	split[6] = code.h >> 28;

	/*
	 * Now we find the highest unit with a non-zero value. If the following
	 * is also non-zero, we use that as our base. If the following is zero,
	 * we simply display the highest unit.
	 */
	for (i = 6; i > 0; i--)
	{
		if (split[i])
			break;
	}
	if (i > 0 && split[i - 1])
	{
		i--;
		capacity = split[i] + (split[i + 1] << 10);
	}
	else
		capacity = split[i];

	printf("\t%s: %lu %s\n", attr, capacity, unit[i + shift]);
}

void 
BiosInfos::printBiosRomSize(UINT8 code1, UINT16 code2) const
{
	if (code1 != 0xFF) {
		UINT64 s = { .l = UINT32(code1 + 1) << 6 };
		prinMemorySize("ROM Size", s, 1);
	} else {
        const char *unit[4] = {
            "MB", "GB", OUT_OF_SPEC, OUT_OF_SPEC
        };
		printf("\tROM Size: %u %s\n", code2 & 0x3FFF, unit[code2 >> 14]);
    }
}

void
BiosInfos::printBiosRuntimeSize(UINT32 code) const
{
	const char *format;

	if (code & 0x000003FF) {
		format = "bytes";
	} else {
		format = "kB";
		code >>= 10;
	}
    
	printf("\tRuntime Size: %u %s\n", code, format);
}